﻿using System.Collections;
using System.Threading;
using System.IO;
using System.Globalization;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
namespace System.Web.Hosting
{
    public partial class WebHostWorkerRequest
    {
        private ArrayList _cachedResponseBodyBytes;
        private int _cachedResponseBodyBytesIoLockCount;
        private int _cachedResponseBodyLength;
        private byte[] _cachedResponseHeaders;
        private int _cachedResponseKeepConnected;
        private byte[] _cachedResponseStatus;
        private bool _chunked;
        private bool _contentLengthSent;
        protected int _contentTotalLength;
        private EndOfSendNotification _endOfRequestCallback;
        private object _endOfRequestCallbackArg;
        private int _endOfRequestCallbackLockCount;
        private Encoding _headerEncoding;
        private RecyclableCharBuffer _headers = new RecyclableCharBuffer();
        private bool _headersSent;
        protected bool _ignoreMinAsyncSize;
        private bool _requestHeadersAvailable;
        protected bool _requiresAsyncFlushCallback;
        private RecyclableCharBuffer _status = new RecyclableCharBuffer();
        private bool _statusSet = true;
        private string[][] _unknownRequestHeaders;

        private void AddBodyToCachedResponse(MemoryBytes bytes)
        {
            if (_cachedResponseBodyBytes == null)
                _cachedResponseBodyBytes = new ArrayList();
            _cachedResponseBodyBytes.Add(bytes);
            _cachedResponseBodyLength += bytes.Size;
        }

        private void AddHeadersToCachedResponse(byte[] status, byte[] header, int keepConnected)
        {
            _cachedResponseStatus = status;
            _cachedResponseHeaders = header;
            _cachedResponseKeepConnected = keepConnected;
        }

        internal void CallEndOfRequestCallbackOnceAfterAllIoComplete()
        {
            if ((_endOfRequestCallback != null) && (Interlocked.Decrement(ref _endOfRequestCallbackLockCount) == 0))
                try { _endOfRequestCallback(this, _endOfRequestCallbackArg); }
                catch { }
        }

        internal override void EndOfRequest()
        {
            FlushCachedResponse(true);
            if (_headers != null)
            {
                _headers.Dispose(); _headers = null;
            }
            if (_status != null)
            {
                _status.Dispose(); _status = null;
            }
            CallEndOfRequestCallbackOnceAfterAllIoComplete();
        }

        private bool _connectionClosed = false;

        private void FlushCachedResponse(bool isFinal)
        {
            bool async = false;
            int minimumLength = 0;
            IntPtr[] bodyFragments = null;
            int[] bodyFragmentLengths = null;
            long num2 = 0L;
            try
            {
                if (_cachedResponseBodyLength > 0)
                {
                    minimumLength = _cachedResponseBodyBytes.Count;
                    bodyFragments = RecyclableArrayHelper.GetIntPtrArray(minimumLength);
                    bodyFragmentLengths = RecyclableArrayHelper.GetIntegerArray(minimumLength);
                    for (int index = 0; index < minimumLength; index++)
                    {
                        var bytes = (MemoryBytes)_cachedResponseBodyBytes[index];
                        bodyFragments[index] = bytes.LockMemory();
                        if (!isFinal || !bytes.IsBufferFromUnmanagedPool)
                            _requiresAsyncFlushCallback = true;
                        if (bytes.UseTransmitFile)
                        {
                            bodyFragmentLengths[index] = -bytes.Size;
                            _ignoreMinAsyncSize = true;
                            num2 += bytes.FileSize;
                        }
                        else
                        {
                            bodyFragmentLengths[index] = bytes.Size;
                            num2 += bytes.Size;
                        }
                    }
                }
                int doneWithSession = (isFinal ? 1 : 0);
                int finalStatus = (isFinal ? (_cachedResponseKeepConnected != 0 ? 2 : 1) : 0);
                _cachedResponseBodyBytesIoLockCount = 2;
                _endOfRequestCallbackLockCount++;
                try { FlushCore(_cachedResponseStatus, _cachedResponseHeaders, _cachedResponseKeepConnected, _cachedResponseBodyLength, minimumLength, bodyFragments, bodyFragmentLengths, doneWithSession, finalStatus, out async); }
                finally
                {
                    if (isFinal)
                        Close();
                }
            }
            finally
            {
                if (!async)
                {
                    _cachedResponseBodyBytesIoLockCount--;
                    _endOfRequestCallbackLockCount--;
                }
                UnlockCachedResponseBytesOnceAfterIoComplete();
                RecyclableArrayHelper.ReuseIntPtrArray(bodyFragments);
                RecyclableArrayHelper.ReuseIntegerArray(bodyFragmentLengths);
            }
        }

        internal void FlushCore(byte[] status, byte[] header, int keepConnected, int totalBodySize, int numBodyFragments, IntPtr[] bodyFragments, int[] bodyFragmentLengths, int doneWithSession, int finalStatus, out bool async)
        {
            async = false;
            var netContext = NetContext;
            if ((netContext == null) || (_closed == true))
                return;
            var r = netContext.Response;
            var s = r.OutputStream;
            for (int index = 0; index < numBodyFragments; index++)
            {
                var b = new byte[bodyFragmentLengths[index]];
                Marshal.Copy(bodyFragments[index], b, 0, b.Length);
                s.Write(b, 0, b.Length);
            }
            //if (doneWithSession == 1)
            //    r.Close();
        }

        //private void FlushCore(bool keepConnected, int numBodyFragments, IntPtr[] bodyFragments, int[] bodyFragmentLengths, int[] bodyFragmentTypes)
        //{
        //    if (!_connectionClosed )
        //    {
        //        int result = UnsafeIISMethods.MgdFlushCore(this._context, keepConnected, numBodyFragments, bodyFragments, bodyFragmentLengths, bodyFragmentTypes);
        //    }
        //}

        public override void FlushResponse(bool finalFlush)
        {
            if (!_headersSent)
                SendHeaders();
            FlushCachedResponse(finalFlush);
        }

        public override int GetTotalEntityBodyLength()
        {
            return _contentTotalLength;
        }

        public override string GetUnknownRequestHeader(string name)
        {
            if (!_requestHeadersAvailable)
                ReadRequestHeaders();
            int length = _unknownRequestHeaders.Length;
            for (int index = 0; index < length; index++)
            {
                if (string.Equals(name, _unknownRequestHeaders[index][0], StringComparison.OrdinalIgnoreCase))
                    return _unknownRequestHeaders[index][1];
            }
            return null;
        }
        public override string[][] GetUnknownRequestHeaders()
        {
            if (!_requestHeadersAvailable)
                ReadRequestHeaders();
            return _unknownRequestHeaders;
        }

        public override bool HeadersSent()
        {
            return _headersSent;
        }

        internal virtual MemoryBytes PackageFile(string filename, long offset64, long length64, bool isImpersonating)
        {
            int offset = Convert.ToInt32(offset64);
            Convert.ToInt32(length64);
            FileStream stream = null;
            MemoryBytes bytes = null;
            try
            {
                stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
                int count = ((int)stream.Length) - offset;
                var buffer = new byte[count];
                int size = stream.Read(buffer, offset, count);
                bytes = new MemoryBytes(buffer, size);
            }
            finally
            {
                if (stream != null)
                    stream.Close();
            }
            return bytes;
        }

        private void ResetCachedResponse()
        {
            _cachedResponseStatus = null;
            _cachedResponseHeaders = null;
            _cachedResponseBodyLength = 0;
            _cachedResponseBodyBytes = null;
            _requiresAsyncFlushCallback = false;
            _ignoreMinAsyncSize = false;
        }

        public override void SendCalculatedContentLength(int contentLength)
        {
            SendCalculatedContentLength((long)contentLength);
        }

        public override void SendCalculatedContentLength(long contentLength)
        {
            if (!_headersSent)
            {
                _headers.Append("Content-Length: ");
                _headers.Append(contentLength.ToString(CultureInfo.InvariantCulture));
                _headers.Append("\r\n");
                _contentLengthSent = true;
            }
        }

        internal virtual void SendEmptyResponse()
        {
        }

        private void SendHeaders()
        {
            if (!_headersSent && _statusSet)
            {
                _headers.Append("\r\n");
                AddHeadersToCachedResponse(_status.GetEncodedBytesBuffer(), _headers.GetEncodedBytesBuffer(_headerEncoding), (_contentLengthSent || _chunked ? 1 : 0));
                _headersSent = true;
            }
        }

        public override void SendKnownResponseHeader(int index, string value)
        {
            if (_headersSent)
                throw new HttpException("Cannot_append_header_after_headers_sent");
            _headers.Append(HttpWorkerRequest.GetKnownResponseHeaderName(index));
            _headers.Append(": ");
            _headers.Append(value);
            _headers.Append("\r\n");
            if (index == 11)
                _contentLengthSent = true;
            else if (((index == 6) && (value != null)) && value.Equals("chunked"))
                _chunked = true;
        }

        public override void SendResponseFromFile(IntPtr handle, long offset, long length)
        {
            if (!_headersSent)
                SendHeaders();
            if (length != 0L)
            {
                FileStream f = null;
                try
                {
                    f = new FileStream(new SafeFileHandle(handle, false), FileAccess.Read);
                    SendResponseFromFileStream(f, offset, length);
                }
                finally
                {
                    if (f != null)
                        f.Close();
                }
            }
        }

        public override void SendResponseFromFile(string filename, long offset, long length)
        {
            if (!_headersSent)
                SendHeaders();
            if (length != 0L)
            {
                FileStream f = null;
                try
                {
                    f = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
                    SendResponseFromFileStream(f, offset, length);
                }
                finally
                {
                    if (f != null)
                        f.Close();
                }
            }
        }

        private void SendResponseFromFileStream(FileStream f, long offset, long length)
        {
            long num = f.Length;
            if (length == -1L)
                length = num - offset;
            if ((offset < 0L) || (length > (num - offset)))
                throw new HttpException("Invalid_range");
            if (length > 0L)
            {
                if (offset > 0L)
                    f.Seek(offset, SeekOrigin.Begin);
                var buffer = new byte[(int)length];
                int size = f.Read(buffer, 0, (int)length);
                if (size > 0)
                    AddBodyToCachedResponse(new MemoryBytes(buffer, size));
            }
        }

        public override void SendResponseFromMemory(IntPtr data, int length)
        {
            SendResponseFromMemory(data, length, false);
        }

        public override void SendResponseFromMemory(byte[] data, int length)
        {
            if (!_headersSent)
                SendHeaders();
            if (length > 0)
                AddBodyToCachedResponse(new MemoryBytes(data, length));
        }

        internal override void SendResponseFromMemory(IntPtr data, int length, bool isBufferFromUnmanagedPool)
        {
            if (!_headersSent)
                SendHeaders();
            if (length > 0)
                AddBodyToCachedResponse(new MemoryBytes(data, length, isBufferFromUnmanagedPool ? BufferType.UnmanagedPool : BufferType.Managed));
        }

        public override void SendStatus(int statusCode, string statusDescription)
        {
            _status.Append(statusCode.ToString());
            _status.Append(" ");
            _status.Append(statusDescription);
            _statusSet = true;
        }

        public override void SendUnknownResponseHeader(string name, string value)
        {
            if (_headersSent)
                throw new HttpException("Cannot_append_header_after_headers_sent");
            _headers.Append(name);
            _headers.Append(": ");
            _headers.Append(value);
            _headers.Append("\r\n");
        }

        public override void SetEndOfSendNotification(EndOfSendNotification callback, object extraData)
        {
            if (_endOfRequestCallback != null)
                throw new InvalidOperationException();
            _endOfRequestCallback = callback;
            _endOfRequestCallbackArg = extraData;
            _endOfRequestCallbackLockCount = 1;
        }

        internal override void SetHeaderEncoding(Encoding encoding)
        {
            _headerEncoding = encoding;
        }

        internal override void TransmitFile(string filename, long offset, long length, bool isImpersonating)
        {
            if (!_headersSent)
                SendHeaders();
            if (length != 0L)
                AddBodyToCachedResponse(PackageFile(filename, offset, length, isImpersonating));
        }

        internal void UnlockCachedResponseBytesOnceAfterIoComplete()
        {
            if (Interlocked.Decrement(ref _cachedResponseBodyBytesIoLockCount) == 0)
            {
                if (_cachedResponseBodyBytes != null)
                {
                    int count = _cachedResponseBodyBytes.Count;
                    for (int index = 0; index < count; index++)
                    {
                        try { ((MemoryBytes)_cachedResponseBodyBytes[index]).UnlockMemory(); }
                        catch { }
                    }
                }
                ResetCachedResponse();
            }
        }
    }
}
